關於 Rails web 應用程式的啟動過程,對於初學者來說並不會是首要目標,大多數還是會先從 MVC 架構開始學起,但如果學習到一定的程度,想要更深入了解 Rails 在幹嘛的話,閱讀 Rails 的核心程式碼,也就是 Railties 是不可少的過程
Railties 簡單來說,就是在做應用程式的初始化,並且把所有相關的套件,例如 Active Record 給載入組合在一起,以我們目前所做的 Mavericks 來說,應用程式初始化要做的事情就是設定 autoload 路徑和資料庫的設定,在實作 Mavericks 啟動流程之前,我們可以先來看 Rails 是怎麼處理的
之前我們提過啟動點,在 config.ru
這個檔案裡面
# config.ru
# This file is used by Rack-based servers to start the application.
require_relative 'config/environment'
run Rails.application
這個檔案做了兩件事情,一個是載入 config/environment.rb
,另一個是利用 Rack 提供的 run 來執行 Rails.application
,相信如果有跟著一起做到今天,這邊大家應該都不陌生,
接著來看看 config/environment.rb
這支檔案,
# config/environment
# Load the Rails application.
require_relative 'application'
# Initialize the Rails application.
Rails.application.initialize!
一樣也是做了兩件事情,一個是載入 application.rb
這支檔案,並且執行 Rails.application.initialize!
,仔細看 Rails 有很貼心的在上面做註解
接著繼續看 application.rb
裡面寫了些什麼
require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Blog
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
# config.load_defaults 6.0
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
end
end
總共 require 兩個部分,一個是 boot.rb
,裡面是關於 gem 的載入相關設定,這裡先略過,另一個是載入 rails/all
,如果你去翻原始碼,會發現 Rails 在這裡載入所有相關的套件,還記得在鐵人賽一開始有提過,Rails 在做的事情,就是將許多套件整合在一起,而 Rails 真正的核心是 railties
(再次強調)
所以對於 Maverciks 來說,我們第一步也需要做差不多的事情,建立一個檔案來負責載入所以的套件,首先建立一個 all.rb
# mavericks/lib/mavericks/all.rb
require 'yaml'
require "mavericks"
require "active_support"
require "active_record"
require "mavericks/routing"
require "mavericks/controller"
接著在 just_do application.rb
那邊也需要修改一下
# 原先是 require 'mavericks'
require 'mavericks/all'
ActiveRecord::Base.establish_connection
ActiveSupport::Dependencies.autoload_paths = Dir["./app/*"]
module JustDo
class Application < Mavericks::Application
end
end
重新跑一次伺服器打開網頁,一切應該會運作正常
做完載入套件後,接下來就是將套件組合在一起使用,另外就是要做初始化設定,目前我們有兩個需要做處理
# 資料庫設定
ActiveRecord::Base.establish_connection
# autoload 設定
ActiveSupport::Dependencies.autoload_paths = Dir["./app/*"]
我們需要有個地方來做這些設定,目前的做法是暫時放在 config/application.rb
,但實作上應該包裝在一個 method 裡面,剛剛有提到 Rails 的做法是在這裡執行
Rails.application.initialize!
那我們一樣就來實作吧,我們需要建立一個 Application 的 Class,這部分在系列文章一開始的時候,我們是寫在 mavericks.rb
裡面,現在我們要搬移出來
建立一個 Application.rb
,並將原先的 mavericks.rb
裡面的程式碼搬移過來
# mavericks/lib/mavericks/application.rb
module Mavericks
class Error < StandardError; end
class Application
# .
# .
# (略)
end
end
然後將 mavericks.rb
做修正,做法跟之前 Active Record 一樣
# mavericks/lib/mavericks.rb
module Mavericks
autoload :Application, 'mavericks/application'
def self.application
Application.instance
end
def self.root
application.root
end
end
這裡做了一個 class method 回傳 Application.instance
,這個 instance 其實就是利用 Mavericks 來生成的專案 application,也就是 just_do 這個應用程式,為了能做到這樣的效果,我們會用到 inherited
這個 Hook method
來取得子類別,並且用這個子類別 new 一個物件
有點複雜?直接來看程式碼
# mavericks/lib/mavericks/application.rb
module Mavericks
class Error < StandardError; end
class Application
# .
# .
# (略)
def self.inherited(klass)
super
@instance = klass.new
end
def self.instance
@instance
end
def initialize!
config_environment_path = caller.first
@root = Pathname.new(File.expand_path("../..", config_environment_path))
raw = @root.join('config/database.yml').read
database_config = YAML.safe_load(raw)
database_adapter = database_config['default']['adapter']
database_name = database_config['development']['database']
ActiveRecord::Base.establish_connection(database_adapter: database_adapter, database_name: database_name)
ActiveSupport::Dependencies.autoload_paths = Dir["#{@root}/app/*"]
end
def root
@root
end
# .
# .
# (略)
end
end
我們如果要透過 Mavericks::Application
來建立 繼承的子類別物件
,就可以透過 inherited
這個 Hook method 來知道是誰繼承了 Mavericks::Application
(這邊繼承的子類別就是 JustDo::Application
),也就是 method 裡面傳的參數 klass
也就是說
@instance = klass.new
實際上就等於
@instance = JustDo::Application.new
接著在 initialize!
這個 method 我們做了一些有趣的事情,像是我們利用 Ruby 的 caller
來取得檔案的位置,caller
會回傳一個陣列裡面包含呼叫這個 method 所經過的檔案路徑,這裡的 caller.first
會回傳 environment.rb
的路徑位置,我們也知道 environment.rb
位於專案目錄底下的 just_do/config/environment.rb
,所以往上兩層就是這個專案的根目錄
# 往上兩層就是專案的根目錄
@root = Pathname.new(File.expand_path("../..", config_environment_path))
有了根目錄的位置以後,要取得其他檔案就相對容易許多
# 透過 @root 知道 database.yml 位置
raw = @root.join('config/database.yml').read
# 透過 @root 可以 autoload 所需要的檔案,例如: controller
ActiveSupport::Dependencies.autoload_paths = Dir["#{@root}/app/*"]
另外我們已經將解析 database.yml
搬到 Application.initialize!
實作,所以原先的active_record/base.rb
也要修改一下,來減少耦合
# mavericks/lib/active_record/base.rb
def self.establish_connection(options)
database_name = options[:database_name]
case options[:database_adapter]
when 'postgresql'
@@connection = ConnectionAdapter::PostgreSQLAdapter.new(database_name)
when 'sqlite'
@@connection = ConnectionAdapter::SQLiteAdapter.new(database_name)
end
end
現在我們已經可以利用 Mavericks.root
來取得專案的根目錄,也可以用 Mavericks.application
來取得應用程式相關資訊,但我們希望可以做到像 Rails 一樣,透過 Rails.env
來設定環境變數,這樣我們就可以隨著環境不同,改變應用程式的資料庫,做法其實很簡單
# mavericks/lib/mavericks.rb
module Mavericks
autoload :Application, 'mavericks/application'
def self.application
Application.instance
end
def self.root
application.root
end
def self.env
ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
end
end
我們這裡加上一個 class method 叫 env
,接著預設給 development
,接著修改一下 application.rb
的 initialize!
def initialize!
config_environment_path = caller.first
@root = Pathname.new(File.expand_path("../..", config_environment_path))
raw = @root.join('config/database.yml').read
database_config = YAML.safe_load(raw)
database_adapter = database_config['default']['adapter']
# 用環境變數來設定目前使用的資料庫
database_name = database_config[Mavericks.env]['database']
ActiveRecord::Base.establish_connection(database_adapter: database_adapter, database_name: database_name)
ActiveSupport::Dependencies.autoload_paths = Dir["#{@root}/app/*"]
end
我們回到 just_do 修改一下專案的程式碼,對於 Mavericks 來說,啟動的順序會是這樣
# just_do/config.ru
# 改為先呼叫 config/environment 做初始化設定
require_relative 'config/environment'
# 改為呼叫 Mavericks.application
run Mavericks.application
# just_do/config/environment.rb
#
# Load the Mavericks application.
require_relative 'application'
# Initialize the Mavericks application.
Mavericks.application.initialize!
# just_do/config/application.rb
require 'mavericks/all'
module JustDo
class Application < Mavericks::Application
end
end
啟動伺服器跑跑看,如果沒有錯誤的話代表成功了!幾乎跟 Rails 一模一樣
Mavericks 程式碼
https://github.com/apayu/mavericks